package com.codefish.framework.spring.beans.annotation;

import com.codefish.framework.spring.beans.exception.BeansException;
import com.codefish.framework.spring.beans.factory.IBeanFactory;
import com.codefish.framework.spring.beans.factory.IBeanFactoryAware;
import com.codefish.framework.spring.beans.factory.config.IConfigurableListableBeanFactory;
import com.codefish.framework.spring.beans.factory.config.IInstantiationAwareBeanPostProcessor;
import com.codefish.framework.spring.core.utils.AssertUtils;
import com.codefish.framework.spring.core.utils.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

/**
 * @author <a href='https://gitee.com/hzf2281'>codefish</a>
 * @version 1.0.0
 * @since 2022/11/06 下午 04:18
 */
public class AutowiredAnnotationBeanPostProcessor implements IBeanFactoryAware, IInstantiationAwareBeanPostProcessor {

    private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new HashSet<>(4);
    private IConfigurableListableBeanFactory beanFactory;

    public AutowiredAnnotationBeanPostProcessor() {
        autowiredAnnotationTypes.add(Autowired.class);
        autowiredAnnotationTypes.add(Value.class);
    }

    public AutowiredAnnotationBeanPostProcessor(IConfigurableListableBeanFactory beanFactory) {
        this();
        this.beanFactory = beanFactory;
    }

    public void processInject(Object bean, String beanName) {
        InjectMetadata metadata = buildAutowiredMetadata(bean.getClass());
        metadata.inject(bean, beanName);
    }

    /**
     * 遍历解析class对象的所有属性和方法，解析{@link Value}和{@link Autowired}注解，
     * 生成对应的{@link InjectMetadata.InjectElement}，放入{@link InjectMetadata}中并返回
     *
     * @param clazz 需要解析的class对象
     * @return 对应的InjectMetadata
     */
    private InjectMetadata buildAutowiredMetadata(Class<?> clazz) {
        InjectMetadata metadata = new InjectMetadata();

        ReflectionUtils.doWithLocalFields(clazz, field -> {
            Annotation targetAnnotation = findAutoWiredAnnotation(field);
            if (!Modifier.isStatic(field.getModifiers())
                    && !Modifier.isFinal(field.getModifiers())) {
                metadata.addInjectElement(new FieldInjectElement(field, getRequired(targetAnnotation)));
            }
        });

        return metadata;
    }

    private boolean getRequired(Annotation annotation) {
        if (annotation instanceof Value) {
            return true;
        }
        if (annotation instanceof Autowired) {
            return ((Autowired) annotation).required();
        }
        return false;
    }

    private Annotation findAutoWiredAnnotation(AccessibleObject ao) {
        Annotation targetAnnotation = null;
        for (Annotation annotation : ao.getAnnotations()) {
            if (autowiredAnnotationTypes.contains(annotation.annotationType())) {
                targetAnnotation = annotation;
                break;
            }
        }
        return targetAnnotation;
    }


    private class FieldInjectElement extends InjectMetadata.InjectElement {

        private final boolean required;


        public FieldInjectElement(Field field, Boolean required) {
            super(field);
            this.required = required;
        }

        @Override
        public Object getResourceToInject(Object target, String requestingBeanName) {
            Set<String> possibleBeanNames = new HashSet<>();
            Object resource = beanFactory.resolveDependency(new DependencyDescriptor((Field) member, required), requestingBeanName, possibleBeanNames);
            if (required) {
                AssertUtils.notNull(resource, "获取的resource不能为null");
            }
            return resource;
        }
    }

//TODO  MethodInjectElement

//    private class MethodInjectElement extends InjectMetadata.InjectElement {
//
//        private final boolean required;
//
//
//        public MethodInjectElement(Method method, Boolean required) {
//            super(method);
//            this.required = required;
//        }
//
//        @Override
//        public Object getResourceToInject(Object target, String requestingBeanName) {
//            List<String> possibleBeanNames = new ArrayList<>();
//            Object resource = beanFactory.resolveDependency(member, requestingBeanName, possibleBeanNames);
//            if (required) {
//                AssertUtils.notNull(resource, "获取的resource不能为null");
//            }
//            return resource;
//        }
//    }


    @Override
    public void setBeanFactory(IBeanFactory beanFactory) {
        this.beanFactory = (IConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        processInject(bean, beanName);
        return true;
    }
}
